classdef ET_PTB < ET_PURSUIT
    
    properties
        
        MEG = 0
        % This is a tally for the trigger, so that the trigger is sent only
        % once
        trigger_tally = [1,1,0];
        
        % Fist letter: 
        % (L)eft-(R)ight
        % (E)nter-(G)o out
        % (A)celerating-(D)ecelerating
        % (N)oisy-(S)mooth'
        
        trigger_codes = struct(...
            'lean',30, ...
            'leas',40, ...
            'ledn',50, ...
            'leds',60, ...
            'lgan',70, ...
            'lgas',80, ...
            'lgdn',90, ...
            'lgds',100, ...
            'rean',110, ...
            'reas',120, ...
            'redn',130, ...
            'reds',140, ...
            'rgan',150, ...
            'rgas',160, ...
            'rgdn',170, ...
            'rgds',180 )
        
        outport = 888
        
    end
    
    
    methods
        function self = ET_PTB()
            % Restart the seed of the random function
            self = self@ET_PURSUIT();
            % Overrides the variable scn
            self.stim       = struct('size',15,'rgb',[255 255 255]);
        end
        
        function startGM(self)
            % Initializes the graphical motor, i.e., cogent or PTB.
            if self.EyeLink
                Eyelink('Initialize');
            end
            % Prevents input into matlab
            % Makes the keyboard insensible to capslock pressing
            if ispc()
            elseif isunix()
                ListenChar(2);
            end
            % Initiliazies the screen an saves the pointer into scn.p
            [self.scn.p]=Screen('OpenWindow',self.scn.mon,0,...
                [0 0 self.scn.w self.scn.h], self.scn.color);
            self.el_pointer.exp.scn.p = self.scn.p;
            HideCursor();
            black=BlackIndex(self.scn.p);
            Screen('FillRect',self.scn.p,black);
            Screen(self.scn.p, 'Flip');
            % Saves the refresh rate
            self.scn.refresh_rat = Screen('GetFlipInterval', self.scn.p);
            % Wait to have any input key pressed
            self.centeredText('Press a key when ready...');
            
            WaitSecs(0.5)
            
            while ~KbCheck()
            end
        end
        
        function runScreen(self)
            
            % Start dialog
            self.startDialog()
            % Preamble
            self.startGM()
            
            stimulusPosition = @(cx,size) [ cx - size,self.scn.h/2 - size, ...
                cx + size,self.scn.h/2 + size];
            
            %KbEventFlush();
            
            for trial = 1:self.num_blocks*self.num_trials
                [t,b]= ind2sub([self.num_trials,self.num_blocks],trial);
                if self.EyeLink;
                    % Try to calibrate. In case of any error cleans up and
                    % report error.
                    try
                        calibrateEyetracker(self.scn.p,self.scn.mon);
                    catch Err
                        self.exit(Err)
                    end
                end
                % Draws the fixation!!
                cx = (self.randomFixation(trial)*(self.scn.w/2)) + (self.scn.w/2);
                stim_position = stimulusPosition(cx,self.stim.size);
                Screen('FillOval',self.scn.p,self.stim.rgb,round(stim_position))
                stim_position = stimulusPosition(cx,self.stim.size/3);
                Screen('FillOval',self.scn.p,[1,1,1],round(stim_position));
                Screen(self.scn.p, 'Flip');
                % Brief stop
                WaitSecs(self.fix.time/2);
                % Send parameters
                if self.EyeLink
                    display('Start.')
                    % Open a file on the eyetracker
                    Eyelink('Openfile', [num2str(self.exp_par.sbj) 'b' num2str(b) ...
                        't' num2str(t) '.edf']);
                    Eyelink('StartRecording');
                    self.sendParameters(trial);
                end
                % Brief stop
                WaitSecs(self.fix.time/2);
                
                if self.MEG
                    t0 = GetSecs();
                    % open parallel port
                    
                    outportb(888,200);  % trigger for start of trial
                    wait(10);
                    outportb(888,0);
                    t1 = GetSecs();
                    t2 = GetSecs();
                end
                if self.EyeLink
                    %                     t2 = GetSecs();
                    Eyelink ('Message', 'START');
                    if self.MEG
                        % Sends time stamps of exacly when were the ports
                        % opened
                        tmp = sprintf('TIME_STAMPS_%0.5f_%0.5f', t1-t0,t2-t0);
                        Eyelink('Message',tmp);
                        % Wait a bit before stimuli are displayed
                        WaitSecs(0.05);
                    end
                end
                c = 1;
                dt = 0;
                % LeTteRBox convention Left - Top - Right - Bottom
                % The sign(self.randomFixation(trial)) is used to put the
                % stimuli in contralateral position from the starting
                % position.
                occluderPosition = [...
                    self.scn.w/2 + self.occluder.X(trial) - self.occluder.W(trial)/2, ...
                    self.occluder.y + self.scn.h/2 - self.occluder.h/2,...
                    self.scn.w/2 + self.occluder.X(trial) + self.occluder.W(trial)/2, ...
                    self.occluder.y + self.scn.h/2 + self.occluder.h/2,...
                    ];
                stimuli = self.generateStimuli(trial);
                
                % It's a tally that say first if dot is coming or going and
                % second in which cycle it is.
                tc = [1,0];
                
                % Calculate occluder in internal coordinates
                occ_l = self.occluder.X(trial)/(self.randomFixation(trial)*(self.scn.w/2));
                % Right side of the occluder
                occ_r = occ_l + abs(self.occluder.W(trial)/2/(self.randomFixation(trial)*(self.scn.w/2)));
                occ_l = occ_l - abs(self.occluder.W(trial)/2/(self.randomFixation(trial)*(self.scn.w/2)));
                % Left side of the occuluder
                
                goal = @(x) x*self.randomFixation(trial)*self.scn.w/2 ...
                + self.scn.w/2;        
                % Occluder to the right
                
                
                % Display loop
                while tc(2) <= self.cycles
                    dt = dt+(1/self.FpC(trial));
                    if c && abs(dt - self.jump.frame(trial)) <= 2/self.FpC(trial)
                        dt = dt + self.jump.m(trial);
                        c = 0;
                    end
                    cx = stimuli(dt,self.FpC(trial));
                    
                    % Keeps a tally
                    if abs(cx-1) < 0.01 && tc(1) ;
                        tc(1) = 0;
                        tc(2) = tc(2) + 1;
                    elseif abs(cx+1) < 0.01 && ~tc(1);
                        tc(1) = 1;
                    end
                    % Relocate in extrinsic coordinates
                    stim_position = stimulusPosition(goal(cx),self.stim.size);
                    % LeTteRBox convention Left - Top - Right - Bottom
                    Screen('FillOval',self.scn.p,self.stim.rgb,stim_position)
                    % Draws a point in the middle
                    %------------------------------------------------------
                    stim_position = stimulusPosition(goal(cx),self.stim.size/3);
                    Screen('FillOval',self.scn.p,[1,1,1],stim_position)
                    
                    % Draw occluder
                    %------------------------------------------------------
                    Screen('FillRect',self.scn.p,self.occluder.rgb, occluderPosition);
                    Screen(self.scn.p, 'Flip');
                    % Send a trigger to the MEG
                    self.trigger(cx,trial,occ_l,occ_r,tc)
                    checkOccluder();
                    
                    [goout,kn] = self.escapeTrial();
                    
                    if goout; break; end
                    
                end
                % Notify end of the recording
                if self.EyeLink
                    Eyelink ('Message', 'END');
                end
                if self.MEG
                    % MEG trigger - end of eyelink recording
                    outportb(888,0);
                end
                % If it breaks it trashes all the information
                if kn; break; end
                % Transtion between trials
                self.trialTransition(trial);
                % Pauses the script and restarts it
                if ~mod(trial,self.pauses)
                    % Pause message
                    self.centeredText('Pause. Press any key to continue.');
                    %KbEventFlush();
                    % Buffer.
                    WaitSecs(0.5);
                    while 1
                        [avail,~,k] = KbCheck();
                        if avail;
                            break
                        end
                    end
                end
                %KbEventFlush();
                
                % Tries to clear the function that produced the stimuli
                %----------------------------------------------------------
                
                tmp = regexp(char(self.stimuli),'(.+)(\()','tokens');
                fprintf(1,'Restart %s\n',tmp{1}{1});
                clear(tmp{1}{1});
            end
            
            if self.EyeLink;
                Eyelink('Shutdown');
            end
            % Return keyboard input to matlab
            if ispc()
                
            elseif isunix()
                ListenChar(0);
            end
            % Shutdown screen
            Screen('CloseAll');
            
            
            function checkOccluder()
                % No eye link
                if self.EyeLink == 0; return; end
                if self.occluder.Xbase(trial) > 3   % i.e. if occ overhangs
                    if self.occluder.X(trial) < cx
                        %Eyelink('Message', 'OCCLUDER');
                    end
                else
                    if self.occluder.X(trial) < cx < self.occluder.End(trial)
                        %Eyelink('Message', 'OCCLUDER');
                    end
                end
            end
            
        end
        
        function trialTransition(self,trial)
            % What is done between trials
            stimulusPosition = @(cx,size) [ cx - size,self.scn.h/2 - size, ...
                cx + size,self.scn.h/2 + size];
            
            cx = (self.randomFixation(trial)*(self.scn.w/2)) + (self.scn.w/2);
            
            stim_position = stimulusPosition(cx,self.stim.size);
            Screen('FillOval',self.scn.p,self.stim.rgb,round(stim_position));
            
            stim_position = stimulusPosition(cx,self.stim.size/3);
            Screen('FillOval',self.scn.p,[1,1,1],round(stim_position));
            Screen(self.scn.p, 'Flip');
            
            WaitSecs(self.fix.time);
            % Saves file
            if self.EyeLink
                Eyelink('Closefile');
                Eyelink('ReceiveFile');
            end
            Screen('FillRect',self.scn.p,[1,1,1]);
            Screen(self.scn.p, 'Flip');
            WaitSecs(self.fix.time*2);
        end
                        
        function trigger(self,cx,trial,occ_l,occ_r,tc)
            
            % cx is the position of the stimulus in interal coordinates
            % trial is the trial number
            % occ_l is the left border of the occluder in interal coordinates
            % occ_l is the right border of the occluder in interal coordinates
            % tc is the tally of the direction of the dot: 0 if its going
            % left to right and 1 if its going right to left. It's also the
            % cycle number counting from 1;
            
            % Determine in which direction is the stimuli going (from right
            % to left or left to right using the tallie that is kept

            if self.MEG
                sendTrigger = @(trigger) outportb(self.outport,trigger);
            else
                % For debbuging
                sendTrigger = @(trigger) fprintf(1,'Trigger: %d\n',trigger);
            end

            if self.randomFixation(trial) < 0
                % THIS IS A REPUGNANT PIECE OF MISERABLE CODA WHICH WILL
                % CAUSE LATER ON.
                % In a mirror, right is left, and left is right
               cx = -cx;
               t = occ_l;
               occ_l = -occ_r;
               occ_r = -t;
               tc(1) = ~tc(1);
            end
            
            % Restarts the tallies and increase the number of trips using
            % the tally from outside every time there was half a loop.
            % This is necessary because you need to remember if you have
            % sent a trigger or not. To know that the dot completed half a
            % cycle you need to see if the value of tc(1) changed. So the
            % value is stored locally in self.trigger_tally(3) and it's
            % compared in each cycle

            
            if tc(1) ~= self.trigger_tally(3)
                self.trigger_tally(1) = 1;
                self.trigger_tally(2) = 1;
                self.trigger_tally(3) = tc(1);
            end
            
            % If its going from left to right.
            if tc(1) == 1
                % The target crossed the occluder limit from the left and
                % the trigger hasn't been sent
                if self.trigger_tally(1) && cx > occ_l
                    % Write notice that the trigger has been sent
                    self.trigger_tally(1) = 0;
                    % Now decide if it's noisy or not and if its in the
                    % accelerating phase or not.
                    if self.noise_matrix(trial) > 0 && occ_l < 0
                        % It's noisy and it's before the center, ergo it's
                        % accelerating
                        sendTrigger(self.trigger_codes.lean);            
                    elseif self.noise_matrix(trial) > 0 && occ_l >= 0
                        % It's noisy but decelerating from left to right
                        sendTrigger(self.trigger_codes.ledn);
                    elseif self.noise_matrix(trial) == 0 && occ_l < 0
                        % Smooth and accelerating, from left to right
                        sendTrigger(self.trigger_codes.leas);
                    elseif self.noise_matrix(trial) == 0 && occ_l >= 0
                        % Smooth and decelarating, from left to right
                        sendTrigger(self.trigger_codes.leds);
                    end
                    wait(10);
                    outportb(888,0);
                end
                
                % The target overpassed the occluder and it's now visible
                if self.trigger_tally(2) && cx > occ_r
                    % Write notice that the trigger has been sent
                    self.trigger_tally(2) = 0;
                    % Now decide if it's noisy or not and if its in the
                    % accelerating phase or not.
                    if self.noise_matrix(trial) > 0 && occ_r <= 0
                        % It's noisy and it's before the center, ergo it's
                        % accelerating
                        sendTrigger(self.trigger_codes.lgan);
                    elseif self.noise_matrix(trial) > 0 && occ_r > 0
                        % It's noisy but decelerating from left to right
                        sendTrigger(self.trigger_codes.lgdn);
                    elseif self.noise_matrix(trial) == 0 && occ_r <= 0
                        % Smooth and accelerating, from left to right
                        sendTrigger(self.trigger_codes.lgas);
                    elseif self.noise_matrix(trial) == 0 && occ_r > 0
                        % Smooth and decelarating, from left to right
                        sendTrigger(self.trigger_codes.lgds);
                    end
                    wait(10);
                    outportb(888,0);
                end
            
            % if its going from right to left
            elseif tc(1) == 0
                
                % The target crossed the occluder limit (now it's occluded)
                % from the right and the trigger hasn't been sent
                if self.trigger_tally(1) && cx < occ_r

                    self.trigger_tally(1) = 0;
                    % Now decide if it's noisy or not and if its in the
                    % accelerating phase or not.
                    if self.noise_matrix(trial) > 0 && occ_r > 0
                        % It's noisy and it's before the center, ergo it's
                        % accelerating
                        sendTrigger(self.trigger_codes.rean);
                    elseif self.noise_matrix(trial) > 0 && occ_r <= 0
                        % It's noisy but decelerating from right to left
                        sendTrigger(self.trigger_codes.redn);
                    elseif self.noise_matrix(trial) == 0 && occ_r > 0
                        % Smooth and accelerating, from right to left
                        sendTrigger(self.trigger_codes.reas);
                    elseif self.noise_matrix(trial) == 0 && occ_r <= 0
                        % Smooth and decelarating, from right to left
                        sendTrigger(self.trigger_codes.reds);
                    end
                    wait(10);
                    outportb(888,0);
                end
                
                % The target overpassed the occluder and it's now visible
                if self.trigger_tally(2) && cx < occ_l
                    % Write notice that the trigger has been sent
                    self.trigger_tally(2) = 0;
                    % Now decide if it's noisy or not and if its in the
                    % accelerating phase or not.
                    if self.noise_matrix(trial) > 0 && occ_l >= 0
                        % It's noisy and it's before the center, ergo it's
                        % accelerating (It is right from the center moving
                        % right to left)
                        sendTrigger(self.trigger_codes.rgan);
                    elseif self.noise_matrix(trial) > 0 && occ_l < 0
                        % It's noisy but decelerating from left to right
                        sendTrigger(self.trigger_codes.rgdn);
                    elseif self.noise_matrix(trial) == 0 && occ_l >= 0
                        % Smooth and accelerating, from left to right
                        sendTrigger(self.trigger_codes.rgas);
                    elseif self.noise_matrix(trial) == 0 && occ_l < 0
                        % Smooth and decelarating, from left to right
                        sendTrigger(self.trigger_codes.rgds);
                    end
                    wait(10);
                    outportb(888,0);
                end
            end
        end
        
    end
end